package ru.compft.aop.profiling;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.util.Arrays;

/**
 * User: ASUS
 * Date: 21.10.12
 * Time: 11:38
 * Компонент для профилирования
 */
@Component
@Aspect
public class ServiceLayerProfiler {
    private static final Logger logger = LoggerFactory.getLogger(ServiceLayerProfiler.class);

    @Before(value = "ru.compft.aop.SystemArchitecture.inServiceLayer()")
    public void doServiceLayerBefore() {
        System.out.println("ServiceLayerProfiler doServiceLayerBefore");
    }

    @Before(value = "ru.compft.aop.SystemArchitecture.serviceOperation()")
    public void doServiceOperationBefore() {
        System.out.println("ServiceLayerProfiler doServiceOperationBefore");
    }

    @Around("ru.compft.aop.SystemArchitecture.inServiceLayer()")
    public Object doServiceLayerProfiling(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("ServiceLayerProfiler doServiceLayerProfiling = " + pjp);
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

    @Around("ru.compft.aop.SystemArchitecture.serviceOperation()")
    public Object doServiceOperationProfiling(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("ServiceLayerProfiler doServiceOperationProfiling = " + pjp);
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

    /**
     * After (finally) advice runs however a matched method execution exits
     */
    @After("ru.compft.aop.SystemArchitecture.inServiceLayer()")
    public void doServiceLayerMethodAfter() {
        System.out.println("ServiceLayerProfiler doServiceLayerMethodAfter");
    }

    @After("ru.compft.aop.SystemArchitecture.serviceOperation()")
    public void doServiceOperationMethodAfter() {
        System.out.println("ServiceLayerProfiler doServiceOperationMethodAfter");
    }

    /**
     * Sometimes you need access in the advice body to the actual value that was returned
     *
     * @param retVal
     */
    @AfterReturning(pointcut = "ru.compft.aop.SystemArchitecture.inServiceLayer()",
            returning = "retVal")
    public void doServiceLayerAfterReturning(Object retVal) {
        System.out.println("ServiceLayerProfiler doServiceLayerAfterReturning. Obj = " + retVal);
        // ...
    }

    @AfterReturning(pointcut = "ru.compft.aop.SystemArchitecture.serviceOperation()",
            returning = "retVal")
    public void doServiceOperationAfterReturning(Object retVal) {
        System.out.println("ServiceLayerProfiler doServiceOperationAfterReturning. Obj = " + retVal);
        // ...
    }

    /**
     * After throwing advice runs when a matched method execution exits by throwing an exception
     */
    @AfterThrowing("ru.compft.aop.SystemArchitecture.inServiceLayer()")
    public void doServiceLayerAfterThrowing() {
        System.out.println("ServiceLayerProfiler doServiceLayerAfterThrowing");
        // ...
    }

    @AfterThrowing("ru.compft.aop.SystemArchitecture.serviceOperation()")
    public void doServiceOperationAfterThrowing() {
        System.out.println("ServiceLayerProfiler doServiceOperationAfterThrowing");
        // ...
    }

    /**
     * Often you want the advice to run only when exceptions of a given type are thrown, and you also often need access
     * to the thrown exception in the advice body
     *
     * @param ex
     */
    @AfterThrowing(pointcut = "ru.compft.aop.SystemArchitecture.inServiceLayer()",
            throwing = "ex")
    public void doServiceLayerAfterThrowing(Exception ex) {
        System.out.println("ServiceLayerProfiler doServiceLayerAfterThrowing. Ex = " + ex);
        // ...
    }

    @AfterThrowing(pointcut = "ru.compft.aop.SystemArchitecture.serviceOperation()",
            throwing = "ex")
    public void doServiceOperationAfterThrowing(Exception ex) {
        System.out.println("ServiceLayerProfiler doserviceOperationAfterThrowing. Ex = " + ex);
        // ...
    }

    /**
     * Профилирование методов
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("ru.compft.aop.SystemArchitecture.serviceOperation()")
    public Object profileServiceMethodAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("ServiceLayerProfiler profileServiceMethodAround = " + joinPoint);
        StopWatch stopWatch = new StopWatch();
        stopWatch.start(); // запускаем таймер

        Object retVal = joinPoint.proceed(); // выполняем метод

        stopWatch.stop(); // останавливаем таймер

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(joinPoint.getTarget().getClass().getName());
        stringBuilder.append(".");
        stringBuilder.append(joinPoint.getSignature().getName());
        stringBuilder.append("(");
        stringBuilder.append(splitArgs(joinPoint.getArgs()));
        stringBuilder.append(")");
        stringBuilder.append(" execution time: ");
        stringBuilder.append(stopWatch.getTotalTimeMillis());
        stringBuilder.append(" ms");

        if (logger.isInfoEnabled())
            logger.info(stringBuilder.toString());

//        System.out.println(stringBuilder.toString());
        return retVal;
    }

    private String splitArgs(Object[] args) {
        StringBuilder stringBuilder = new StringBuilder();
        if (args != null) {
            stringBuilder.append(Arrays.asList(args));
//            for (Object arg : args) {
//                stringBuilder.append(arg).append(", ");
//            }
        }
        return stringBuilder.toString();
    }
}
